 /*

  xmunipack - astrometry

  Copyright © 2011-2 F.Hroch (hroch@physics.muni.cz)

  This file is part of Munipack.

  Munipack is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.
  
  Munipack is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with Munipack.  If not, see <http://www.gnu.org/licenses/>.

*/

#include "xmunipack.h"
#include <wx/wx.h>
#include <wx/animate.h>
#include <wx/collpane.h>
#include <wx/statline.h>
#include <wx/dataview.h>
#include <wx/regex.h>
#include <wx/sstream.h>
#include <wx/filefn.h> 
#include <cfloat>

#define ARC_SEC "\"/pix"
#define ARC_MIN "'/pix"
#define ARC_DEG "°/pix"

#define ARCSEC "arcsec"
#define ARCMIN "arcmin"
#define ARCDEG "deg"


using namespace std;


// --- xStatus

class xStatus: public wxWindow
{
private:

  wxString text;
  wxBitmap bitmap;

  void UpdateLayout() 
  {
    wxFont sf(*wxSMALL_FONT);

    wxSizer *topsizer = GetSizer();
    topsizer->Clear(true);
    topsizer->Add(new wxStaticBitmap(this,wxID_ANY,bitmap),wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
    wxStaticText *label = new wxStaticText(this,wxID_ANY,text);
    label->SetFont(sf);
    topsizer->Add(label,wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL).Expand().Border(wxLEFT|wxRIGHT));
    Layout();
  }


public:
  xStatus(wxWindow *w, wxWindowID id, const wxString& t =wxEmptyString, const wxBitmap& b =wxNullBitmap):
    wxWindow(w,id), text(t), bitmap(b) 
  {    
    wxBoxSizer *topsizer = new wxBoxSizer(wxHORIZONTAL);
    SetSizer(topsizer);
    UpdateLayout();
  }

  void SetText(const wxString& t)
  {
    text = t;
    bitmap = wxNullBitmap;
    wxLogDebug(text);
    UpdateLayout();
  }

  void SetBitmap(const wxBitmap& b)
  {
    text = wxEmptyString;
    bitmap = b;
    UpdateLayout();
  }

  void SetTitle(const wxString& t, const wxBitmap& b)
  {
    text = t;
    bitmap = b;
    UpdateLayout();    
  }

};


// --- MuniAstrometry


MuniAstrometry::MuniAstrometry(wxWindow *w, MuniConfig *c): 
  wxDialog(w,wxID_ANY,"Astrometry",wxDefaultPosition,wxDefaultSize,wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER),
  config(c), throbber(new wxAnimationCtrl(this,wxID_ANY,config->throbber)),
  catbutt(0),fpick(0),choice_alpha(0),choice_delta(0),
  timer(this),pipe(this),reftype(ID_ASTRO_REF),
  nmatch(config->astrometry_nmatch),maxmatch(config->astrometry_maxmatch),maglim(18.0),
  output_units(config->astrometry_units), 
  init_par(true),init_fit(true), enable_backup(true),running(false)
{
  SetIcon(config->munipack_icon);

  CreateControls();

  ftable->Enable(false);
  fitbutt->Enable(false);

  throbber->Show(false);

  Bind(wxEVT_CLOSE_WINDOW,&MuniAstrometry::OnClose,this);

  rakey = "RAJ2000";
  deckey = "DEJ2000";
  magkey = "f.mag";
  //magkey = "Vmag";
}


MuniAstrometry::~MuniAstrometry()
{
  // delete temps

  config->astrometry_units = output_units;
  config->astrometry_nmatch = nmatch;
  config->astrometry_maxmatch = maxmatch;

  if( !vofile.IsEmpty() )
    wxRemoveFile(vofile);
}



void MuniAstrometry::OnClose(wxCloseEvent& event)
{
  wxLogDebug("MuniAstrometry::OnClose");

  if( running ) {
    pipe.Stop();
    wxQueueEvent(this,event.Clone());
  }
  else {

    // to clear of map
    MuniAstrometryEvent ev(EVT_ASTROMETRY);
    ev.astrometry = false;
    wxQueueEvent(GetParent(),ev.Clone());

    //    wxQueueEvent(GetParent(),event.Clone());
    //    Destroy();
    event.Skip();
  }
}


void MuniAstrometry::CreateControls()
{
  wxSizerFlags lf, cf, uf, sl, rl;
  lf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT);
  cf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT);
  uf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT);
  rl.Align(wxALIGN_CENTER_VERTICAL).Border(wxRIGHT);
  sl.Border().Expand();

  wxStaticText *label;

  wxFont bf(*wxNORMAL_FONT);
  bf.SetWeight(wxFONTWEIGHT_BOLD);

  wxFont sf(*wxSMALL_FONT);

  // title
  wxBoxSizer *topsizer = new wxBoxSizer(wxVERTICAL);
  wxBoxSizer *st = new wxBoxSizer(wxHORIZONTAL);

  MuniArtIcons ico1(wxART_MESSAGE_BOX,wxSize(48,48));
  st->Add(new wxStaticBitmap(this,wxID_ANY,ico1.Icon(wxART_EXECUTABLE_FILE)),wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
  wxStaticText *title = new wxStaticText(this,wxID_ANY,"Astrometry");
  title->SetFont(bf);
  st->Add(title,wxSizerFlags().DoubleBorder().Center());
  topsizer->Add(st,wxSizerFlags().Border().Center());


  // reference
  topsizer->Add(new wxStaticLine(this,wxID_ANY),sl);

  wxBoxSizer *ts = new wxBoxSizer(wxHORIZONTAL);

  label = new wxStaticText(this,wxID_ANY,"Reference:");
  label->SetFont(sf);
  ts->Add(label,wxSizerFlags().DoubleBorder(wxLEFT|wxRIGHT));

  wxRadioButton *rref, *rcat, *rvo;
  rref = new wxRadioButton(this,ID_ASTRO_REF,"Image",wxDefaultPosition,wxDefaultSize,wxRB_GROUP);
  rcat = new wxRadioButton(this,ID_ASTRO_CAT,"Table");
  rvo = new wxRadioButton(this,ID_ASTRO_VO,"Catalogues");
  ts->Add(rref,rl);
  ts->Add(rcat,rl);
  ts->Add(rvo,rl);
  topsizer->Add(ts,wxSizerFlags().Expand());

  refsizer = new wxBoxSizer(wxVERTICAL);
  RefLayout();
  topsizer->Add(refsizer,wxSizerFlags().Center());

  // parameters

  topsizer->Add(new wxStaticLine(this,wxID_ANY),sl);
  label = new wxStaticText(this,wxID_ANY,"Parameters:");
  label->SetFont(sf);
  topsizer->Add(label,wxSizerFlags().DoubleBorder(wxLEFT));


  wxFlexGridSizer *grid = new wxFlexGridSizer(2);
  grid->AddGrowableCol(1);
  
  label = new wxStaticText(this,wxID_ANY,"Projection:");
  grid->Add(label,lf);
  wxArrayString projections;
  projections.Add("Gnomonic");
  wxChoice *proj = new wxChoice(this,wxID_ANY,wxDefaultPosition,wxDefaultSize,projections);
  proj->SetSelection(0);
  grid->Add(proj,cf);

  label = new wxStaticText(this,wxID_ANY,"Scale:");
  grid->Add(label,lf);

  wscale = new wxSpinCtrlDouble(this,wxID_ANY,"",wxDefaultPosition,wxDefaultSize,
				wxSP_ARROW_KEYS|wxTE_PROCESS_ENTER,0.0,100.0,1.0,0.01);
  sunit = new wxButton(this,wxID_ANY,ARC_SEC,wxDefaultPosition,wxDefaultSize,wxBU_EXACTFIT);

  wxBoxSizer *ss = new wxBoxSizer(wxHORIZONTAL);
  ss->Add(wscale);
  ss->Add(sunit);
  grid->Add(ss,cf);

  label = new wxStaticText(this,wxID_ANY,"Position Angle:");
  grid->Add(label,lf);
  ss = new wxBoxSizer(wxHORIZONTAL);
  wangle = new wxSpinCtrlDouble(this,wxID_ANY,"",wxDefaultPosition,wxDefaultSize,
				wxSP_ARROW_KEYS|wxTE_PROCESS_ENTER,0.0,360.0,0.0,0.1);
  ss->Add(wangle);
  ss->Add(new wxStaticText(this,wxID_ANY,"[°]"),uf);
  grid->Add(ss,cf);

  label = new wxStaticText(this,wxID_ANY,"Center in α:");
  grid->Add(label,lf);
  ss = new wxBoxSizer(wxHORIZONTAL);
  acenter = new wxSpinCtrlDouble(this,wxID_ANY,"",wxDefaultPosition,wxDefaultSize,
				wxSP_ARROW_KEYS|wxTE_PROCESS_ENTER,0.0,360.0,0.0,0.001);
  ss->Add(acenter);
  ss->Add(new wxStaticText(this,wxID_ANY,"[°]"),uf);
  grid->Add(ss,cf);

  label = new wxStaticText(this,wxID_ANY,"Center in δ:");
  grid->Add(label,lf);
  ss = new wxBoxSizer(wxHORIZONTAL);
  dcenter = new wxSpinCtrlDouble(this,wxID_ANY,"",wxDefaultPosition,wxDefaultSize,
				wxSP_ARROW_KEYS|wxTE_PROCESS_ENTER,-90.0,90.0,0.0,0.001);
  ss->Add(dcenter);
  ss->Add(new wxStaticText(this,wxID_ANY,"[°]"),uf);
  grid->Add(ss,cf);

  topsizer->Add(grid,wxSizerFlags().Center());

  // Fit

  topsizer->Add(new wxStaticLine(this,wxID_ANY),wxSizerFlags().Border(wxTOP|wxLEFT|wxRIGHT).Expand());

  wxBoxSizer *fsizer = new wxBoxSizer(wxVERTICAL);
  
  ftable = new wxDataViewListCtrl(this,wxID_ANY);
  ftable->AppendTextColumn("Residuals");
  ftable->AppendTextColumn("Scale");
  ftable->AppendTextColumn("Pos. Ang.");
  ftable->AppendTextColumn("α");
  ftable->AppendTextColumn("δ");
  fsizer->Add(ftable,wxSizerFlags(1).Border(wxLEFT|wxRIGHT).Expand());

  topsizer->Add(fsizer,wxSizerFlags().Border().Expand());

  MuniArtIcons ico(wxART_FRAME_ICON,wxSize(16,16));
  status = static_cast<wxWindow *>(new xStatus(this,wxID_ANY,"Detected stars unavailable.",ico.Icon(wxART_WARNING)));
  topsizer->Add(status,wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_CENTER).Border());

  wxCollapsiblePane *collPane = new wxCollapsiblePane(this,wxID_ANY,"Options");
  wxWindow *win = collPane->GetPane();

  wxFlexGridSizer *gsizer = new wxFlexGridSizer(2);
  gsizer->AddGrowableCol(1);

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Parameters:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL));
  wxCheckBox *init_params = new wxCheckBox(win,wxID_ANY,"Init by Reference");
  gsizer->Add(init_params,cf);
  init_params->SetValue(init_par);
  init_params->SetToolTip("Parameters are inited from Reference (averaged coordinates as center, scale as the second moment).");

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Create Backups:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL));
  wxCheckBox *check_backup = new wxCheckBox(win,wxID_ANY,"Enable");
  check_backup->SetToolTip("Backup are created. Switch-off may lead to any data LOST!");
  gsizer->Add(check_backup,wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT));
  check_backup->SetValue(true);

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Output Units:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT));
  wxArrayString xunits;
  xunits.Add(ARCSEC);
  xunits.Add(ARCMIN);
  xunits.Add(ARCDEG);
  wxChoice *ounits = new wxChoice(win,wxID_ANY,wxDefaultPosition,wxDefaultSize,xunits);
  ounits->SetToolTip("Units for list of residuals and scale in FITS header list.");
  gsizer->Add(ounits,wxSizerFlags().Left());
  ounits->SetSelection(ounits->FindString(output_units));

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Match Sequence:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT));
  wxSpinCtrl *snmatch = new wxSpinCtrl(win,wxID_ANY,wxEmptyString,wxDefaultPosition,wxDefaultSize,
				       wxSP_ARROW_KEYS,3,100,nmatch);
  gsizer->Add(snmatch,wxSizerFlags().Left());

  gsizer->Add(new wxStaticText(win,wxID_ANY,"Full Sequence:"),
	      wxSizerFlags().Border().Align(wxALIGN_RIGHT));
  wxSpinCtrl *smmatch = new wxSpinCtrl(win,wxID_ANY,wxEmptyString,wxDefaultPosition,wxDefaultSize,
				       wxSP_ARROW_KEYS,5,666,maxmatch);
  gsizer->Add(smmatch,wxSizerFlags().Left());
  
  snmatch->SetToolTip("Count of objects in the match sequence. Set under 5 leads to an uncertain matching, over 20 is very slow.");
  smmatch->SetToolTip("Maximum of objects in the match sequence. Crowded fields requires high values.");

  win->SetSizer(gsizer);
  gsizer->SetSizeHints(win);
  topsizer->Add(collPane,wxSizerFlags().Expand());

  wxStdDialogButtonSizer *buttsize = 
    static_cast<wxStdDialogButtonSizer *>(CreateButtonSizer(wxAPPLY|wxCANCEL));

  fitbutt = new wxButton(this,wxID_ANY,"FIT");

  if( buttsize ) {
    wxBoxSizer *bsizer = new wxBoxSizer(wxHORIZONTAL);
    bsizer->Add(throbber,wxSizerFlags().ReserveSpaceEvenIfHidden().Align(wxALIGN_LEFT).Border(wxRIGHT));
    bsizer->Add(fitbutt,wxSizerFlags().Align(wxALIGN_LEFT).TripleBorder(wxRIGHT));
    bsizer->Add(buttsize,wxSizerFlags().Align(wxALIGN_RIGHT));
    topsizer->Add(bsizer,wxSizerFlags().Expand().Border());
    SetAffirmativeId(wxAPPLY);
  }

  SetSizerAndFit(topsizer);

  Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometry::OnApply,this,wxID_APPLY);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometry::OnCancel,this,wxID_CANCEL);
  Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometry::OnFit,this,fitbutt->GetId());

  Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED,&MuniAstrometry::OnRefType,this,ID_ASTRO_REF);
  Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED,&MuniAstrometry::OnRefType,this,ID_ASTRO_CAT);
  Bind(wxEVT_COMMAND_RADIOBUTTON_SELECTED,&MuniAstrometry::OnRefType,this,ID_ASTRO_VO);

  Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometry::OnPopScaleUnit,this,sunit->GetId());
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniAstrometry::OnScaleUnit,this,ID_SCALE_ARCSEC);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniAstrometry::OnScaleUnit,this,ID_SCALE_ARCMIN);
  Bind(wxEVT_COMMAND_MENU_SELECTED,&MuniAstrometry::OnScaleUnit,this,ID_SCALE_ARCDEG);
  Bind(wxEVT_COMMAND_CHECKBOX_CLICKED,&MuniAstrometry::OnInitPar,this,init_params->GetId());

  Bind(wxEVT_COMMAND_TEXT_UPDATED,&MuniAstrometry::OnTextDouble,this,wangle->GetId());
  Bind(wxEVT_COMMAND_TEXT_UPDATED,&MuniAstrometry::OnTextDouble,this,wscale->GetId());
  Bind(wxEVT_COMMAND_TEXT_UPDATED,&MuniAstrometry::OnTextDouble,this,acenter->GetId());
  Bind(wxEVT_COMMAND_TEXT_UPDATED,&MuniAstrometry::OnTextDouble,this,dcenter->GetId());

  Bind(wxEVT_COMMAND_TEXT_ENTER,&MuniAstrometry::OnTextDouble,this,wangle->GetId());
  Bind(wxEVT_COMMAND_TEXT_ENTER,&MuniAstrometry::OnTextDouble,this,wscale->GetId());
  Bind(wxEVT_COMMAND_TEXT_ENTER,&MuniAstrometry::OnTextDouble,this,acenter->GetId());
  Bind(wxEVT_COMMAND_TEXT_ENTER,&MuniAstrometry::OnTextDouble,this,dcenter->GetId());

  Bind(wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED,&MuniAstrometry::OnSpinDouble,this,wscale->GetId());
  Bind(wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED,&MuniAstrometry::OnSpinDouble,this,wangle->GetId());
  Bind(wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED,&MuniAstrometry::OnSpinDouble,this,acenter->GetId());
  Bind(wxEVT_COMMAND_SPINCTRLDOUBLE_UPDATED,&MuniAstrometry::OnSpinDouble,this,dcenter->GetId());

  Bind(wxEVT_COMMAND_CHECKBOX_CLICKED,&MuniAstrometry::OnCheckBackup,this,check_backup->GetId());
  Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometry::OnChoiceUnits,this,ounits->GetId());
  Bind(wxEVT_COMMAND_SPINCTRL_UPDATED,&MuniAstrometry::OnSpinNMatch,this,snmatch->GetId());
  Bind(wxEVT_COMMAND_SPINCTRL_UPDATED,&MuniAstrometry::OnSpinMMatch,this,smmatch->GetId());

}


void MuniAstrometry::OnRefType(wxCommandEvent& event)
{
  reftype = event.GetId();
  RefLayout();
  Layout();
}

void MuniAstrometry::RefLayout()
{
  wxSizerFlags lf, cf, uf, sl, rl;
  lf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_RIGHT);
  cf.Align(wxALIGN_CENTER_VERTICAL|wxALIGN_LEFT);

  if( catbutt ) {
    Unbind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometry::OnCatalogue,this,catbutt->GetId());
    catbutt->Destroy();
  }

  if( fpick ) {
    Unbind(wxEVT_COMMAND_FILEPICKER_CHANGED,&MuniAstrometry::OnRefFile,this,fpick->GetId());
    fpick->Destroy();
  }

  if( choice_alpha && choice_delta ) {
    Unbind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometry::OnChoice,this,ID_ASTRO_CH_ALPHA);
    Unbind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometry::OnChoice,this,ID_ASTRO_CH_DELTA);
    choice_alpha->Destroy();
    choice_delta->Destroy();
    key_alpha.Clear();
    key_delta.Clear();
  }
    
  refsizer->Clear(true);
  fpick = 0;
  catbutt = 0;
  choice_alpha = 0;
  choice_delta = 0;

  if( reftype == ID_ASTRO_REF || reftype == ID_ASTRO_CAT ) {

    wxBoxSizer *rfsizer = new wxBoxSizer(wxHORIZONTAL);
    wxStaticText *flabel = new wxStaticText(this,wxID_ANY,"File:");
    rfsizer->Add(flabel,lf);
    fpick = new wxFilePickerCtrl(this,wxID_ANY,"","Select A File",
				 "FITS files "+config->dirmask+")|"+
				 config->dirmask+"| All files (*)|*");
    rfsizer->Add(fpick,wxSizerFlags().Expand());
    refsizer->Add(rfsizer,wxSizerFlags().Border().Center());

    Bind(wxEVT_COMMAND_FILEPICKER_CHANGED,&MuniAstrometry::OnRefFile,this,fpick->GetId());
    
  }
  else if( reftype == ID_ASTRO_VO ) {

    catbutt = new wxButton(this,wxID_ANY,"Search Catalogues");
    refsizer->Add(catbutt,wxSizerFlags().Center().Border());
    Bind(wxEVT_COMMAND_BUTTON_CLICKED,&MuniAstrometry::OnCatalogue,this,catbutt->GetId());

  }

}


void MuniAstrometry::OnRefFile(wxFileDirPickerEvent& event)
{
  reffile = event.GetPath();

  if( reftype == ID_ASTRO_CAT ) {

    wxArrayString cols = FitsColumns(reffile);

    wxBoxSizer *s = new wxBoxSizer(wxHORIZONTAL);
    s->Add(new wxStaticText(this,wxID_ANY,"Key α:"),wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
    choice_alpha = new wxChoice(this,ID_ASTRO_CH_ALPHA,wxDefaultPosition,wxDefaultSize,cols);
    s->Add(choice_alpha,wxSizerFlags().DoubleBorder(wxRIGHT));
    s->Add(new wxStaticText(this,wxID_ANY,"Key δ:"),wxSizerFlags().Align(wxALIGN_CENTER_VERTICAL));
    choice_delta = new wxChoice(this,ID_ASTRO_CH_DELTA,wxDefaultPosition,wxDefaultSize,cols);
    s->Add(choice_delta);
    refsizer->Add(s,wxSizerFlags().Center());
    Layout();
    Fit();  

    Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometry::OnChoice,this,ID_ASTRO_CH_ALPHA);
    Bind(wxEVT_COMMAND_CHOICE_SELECTED,&MuniAstrometry::OnChoice,this,ID_ASTRO_CH_DELTA);

    int i1 = wxNOT_FOUND;
    int i2 = wxNOT_FOUND;

    for(size_t i = 0; i < cols.GetCount(); i++)
      if( cols[i].StartsWith("RA") ) {
	i1 = i;
	key_alpha = cols[i];
	break;
      }
    
    for(size_t i = 0; i < cols.GetCount(); i++)
      if( cols[i].StartsWith("DE") ) {
	i2 = i;
	key_delta = cols[i];
	break;
      }
    
    choice_alpha->SetSelection(i1);
    choice_delta->SetSelection(i2);
  }

  LoadCatalogue(reffile);
}

void MuniAstrometry::OnCatalogue(wxCommandEvent& event)
{
  vector<VOResolv> vores;
  vores.push_back(VOResolv("UCAC 3","http://vizier.u-strasbg.fr/viz-bin/votable/-A?-source=I/315&",
			   "RAJ2000","DEJ2000","f.mag"));
  vores.push_back(VOResolv("GSC 2.3","http://vizier.u-strasbg.fr/viz-bin/votable/-A?-source=I/305&",
			   "RAJ2000","DEJ2000","Vmag"));
  
  MuniCone cone(this,config,vores);
  if( cone.ShowModal() == wxID_OK ) {
    vofile = wxFileName::CreateTempFileName("xmunipack-astrometer-search_");
    wxCopyFile(cone.GetPath(),vofile);
    key_alpha = cone.GetKeyAlpha();
    key_delta = cone.GetKeyDelta();
    LoadCatalogue(vofile);
  }
}

void MuniAstrometry::OnChoice(wxCommandEvent& event)
{
  if( event.GetId() == ID_ASTRO_CH_ALPHA ) 
    key_alpha = event.GetString();
  if( event.GetId() == ID_ASTRO_CH_DELTA ) 
    key_delta = event.GetString();  
}

void MuniAstrometry::OnChoiceUnits(wxCommandEvent& event)
{
  if( event.GetString() == ARC_DEG )
    output_units = "deg";
  else if( event.GetString() == ARC_MIN )
    output_units = "arcmin";
  else
    output_units = "arcsec";
}

void MuniAstrometry::OnCheckBackup(wxCommandEvent& event)
{
  enable_backup = event.IsChecked();
}

void MuniAstrometry::OnSpinNMatch(wxSpinEvent& event)
{
  nmatch = event.GetPosition();
}

void MuniAstrometry::OnSpinMMatch(wxSpinEvent& event)
{
  maxmatch = event.GetPosition();
}

void MuniAstrometry::OnApply(wxCommandEvent& event)
{
  Save(outfile);

  //  SetReturnCode(wxID_APPLY);
  // Close();
  //  wxQueueEvent(GetParent(),new wxCloseEvent(wxEVT_CLOSE_WINDOW,this->GetId()));
}

void MuniAstrometry::OnCancel(wxCommandEvent& event)
{
  SetReturnCode(wxID_CANCEL);
  wxQueueEvent(GetParent(),new wxCloseEvent(wxEVT_CLOSE_WINDOW,this->GetId()));
  //  Close();
}

void MuniAstrometry::OnPopScaleUnit(wxCommandEvent& event)
{
  wxMenu popup;
  popup.AppendRadioItem(ID_SCALE_ARCSEC,ARC_SEC);
  popup.AppendRadioItem(ID_SCALE_ARCMIN,ARC_MIN);
  popup.AppendRadioItem(ID_SCALE_ARCDEG,ARC_DEG);

  PopupMenu(&popup);
}

void MuniAstrometry::OnScaleUnit(wxCommandEvent& event)
{
  switch(event.GetId()){
  case ID_SCALE_ARCSEC: sunit->SetLabel(ARC_SEC); wscale->SetValue(3600.*scale); return;
  case ID_SCALE_ARCMIN: sunit->SetLabel(ARC_MIN); wscale->SetValue(60.*scale);   return;
  case ID_SCALE_ARCDEG: sunit->SetLabel(ARC_DEG); wscale->SetValue(scale);       return;
  }
}

void MuniAstrometry::OnInitPar(wxCommandEvent& event)
{
  init_par = event.IsChecked();
}

void MuniAstrometry::SetFile(const wxString& f)
{
  file = f;
}

void MuniAstrometry::SetArray(const FitsArray& a)
{
  array = a;
  Reset();
}

void MuniAstrometry::SetDetectedSources(const FitsTable& t)
{
  stars = t;

  if( stars.IsOk() ) {
    ftable->Enable();
    fitbutt->Enable();
  }
}

// Generally, the name will be specify HDU: name[X]
void MuniAstrometry::SetOutput(const wxString& name)
{
  outfile = name;
}

void MuniAstrometry::Reset()
{
  wscale->SetValue(wxEmptyString);
  wangle->SetValue(wxEmptyString);
  acenter->SetValue(wxEmptyString);
  dcenter->SetValue(wxEmptyString);

  if( array.IsOk() ) {
    wxASSERT(array.Naxis() == 2);
    xoff = array.Naxes(0)/2.0;
    yoff = array.Naxes(1)/2.0;
  }
}

int MuniAstrometry::GetColIndex(const FitsTable& cat, const wxString& value)
{
  for(size_t i = 0; i < cat.GetCount(); i++) {
    wxString rec(cat.Item(i));
    wxString key,val,com;
    
    FitsHeader::ParseRecord(rec,key,val,com);
    
    if( val == value ) {
      key.Replace("TTYPE","");
      long i;
      if( key.ToLong(&i) )
	return i - 1;

    }
  }
  return -1;
}

void MuniAstrometry::LoadCatalogue(const wxString& filename)
{
  FitsFile fits(filename);
  
  if( ! fits.IsOk() ) return;

  catfile = filename;

  for(size_t i = 0; i < fits.HduCount(); i++) {

    if( ! fits.IsOk() || fits.Hdu(i).Type() != HDU_TABLE ) continue;

    catalogue = fits.Hdu(i);
    //    wxLogDebug("%d %d",(int)t.Nrows(),(int)t.Ncols());

    // inform user
    wxString a;
    a.Printf("%d stars in list.",(int)catalogue.Nrows());
    static_cast<xStatus *>(status)->SetText(a);
    Layout();


    // finds magnitude key
    int ira = GetColIndex(catalogue,rakey);
    int idec = GetColIndex(catalogue,deckey);


    if( init_par && ira >= 0 && idec >= 0 ) {

      double xx,yy,sx,sy;
      xx = 0; yy = 0; sx = 0; sy = 0;

      int n = catalogue.Nrows();
      const double *a = catalogue.GetColumn(ira).GetCol_double();
      const double *d = catalogue.GetColumn(idec).GetCol_double();


      for(int i = 0; i < n; i++) {

	double x = a[i];
	double y = d[i];
	xx = xx + x;
	yy = yy + y;
	sx = sx + x*x;
	sy = sy + y*y;

      }
	
      if( n > 0 ) {

	alpha = xx/n;
	delta = yy/n;

	acenter->SetValue(alpha);
	dcenter->SetValue(delta);

	sx = sqrt(sx/n);
	//	sy = sqrt(sy/n);
	//	wxLogDebug("%f %d %f",sy,n,delta);
	sy = sqrt(sy/n - delta*delta);

	double r = sy; // in declination, there is no scale contraction

	if( yoff ) {
	  scale = r/yoff;

	  //	  wxLogDebug("%f %f %f",(float)alpha,(float)delta,(float)scale);

	  if( sunit->GetLabel() == ARC_SEC )
	    wscale->SetValue(3600.*scale);
	  else if( sunit->GetLabel() == ARC_MIN )
	    wscale->SetValue(60.*scale);
	  else 
	    wscale->SetValue(scale);

	  MuniAstrometryEvent ev(EVT_ASTROMETRY);
	  ev.astrometry = true;
	  ev.proj = proj;
	  ev.scale = scale;
	  ev.angle = angle;
	  ev.xcen = xoff;
	  ev.ycen = yoff;
	  ev.acen = alpha;
	  ev.dcen = delta;
	  ev.layer = CreateMap();
	  wxQueueEvent(GetParent(),ev.Clone());
	}

	return;
      }

    }
  }
  
}

void MuniAstrometry::OnSpinDouble(wxSpinDoubleEvent& event)
{

  if( event.GetId() == wangle->GetId() )
    angle = event.GetValue();
  else if( event.GetId() == acenter->GetId() )
    alpha = event.GetValue();
  else if( event.GetId() == dcenter->GetId() )
    delta = event.GetValue();
  else if( event.GetId() == wscale->GetId() ) {
    if( sunit->GetLabel() == ARC_SEC )
      scale = event.GetValue()/3600.0;
    else if( sunit->GetLabel() == ARC_MIN )
      scale = event.GetValue()/60.0;
    else 
      scale = event.GetValue();
  }

  MuniAstrometryEvent ev(EVT_ASTROMETRY);
  ev.astrometry = true;
  ev.proj = proj;
  ev.scale = scale;
  ev.angle = angle;
  ev.xcen = xoff;
  ev.ycen = yoff;
  ev.acen = alpha;
  ev.dcen = delta;
  ev.layer = CreateMap();
  wxQueueEvent(GetParent(),ev.Clone());

  
}

void MuniAstrometry::OnTextDouble(wxCommandEvent& event)
{
  double x;

  if( ! event.GetString().ToDouble(&x) ) return;

  if( event.GetId() == wangle->GetId() )
    angle = x;
  else if( event.GetId() == acenter->GetId() )
    alpha = x;
  else if( event.GetId() == dcenter->GetId() )
    delta = x;
  else if( event.GetId() == wscale->GetId() ) {
    if( sunit->GetLabel() == ARC_SEC )
      scale = x/3600.0;
    else if( sunit->GetLabel() == ARC_MIN )
      scale = x/60.0;
    else 
      scale = x;
  }

  MuniAstrometryEvent ev(EVT_ASTROMETRY);
  ev.astrometry = true;
  ev.proj = proj;
  ev.scale = scale;
  ev.angle = angle;
  ev.xcen = xoff;
  ev.ycen = yoff;
  ev.acen = alpha;
  ev.dcen = delta;
  ev.layer = CreateMap();
  wxQueueEvent(GetParent(),ev.Clone());
}


MuniLayer MuniAstrometry::CreateMap()
{
  const int ltic = 10;

  vector<wxObject *> objects;

  // central cross
  objects.push_back(new MuniDrawLine(xoff-ltic,yoff,xoff+ltic,yoff));
  objects.push_back(new MuniDrawLine(xoff,yoff-ltic,xoff,yoff+ltic));

  const int rrose = 137;
  const double rad = 57.3;

  // compass rose
  for(int i = 0; i < 36; i++) {
    double f = 10.0*i;
    double r1 = rrose;
    double r2 = rrose + ltic;
    double x1 = xoff + r1*cos(f/rad);
    double y1 = yoff + r1*sin(f/rad);
    double x2 = xoff + r2*cos(f/rad);
    double y2 = yoff + r2*sin(f/rad);
    objects.push_back(new MuniDrawLine(x1,y1,x2,y2));
  }

  // big cross
  for(int i = 0; i < 4; i++) {
    double f = 90.0*i - angle;
    double r1 = 3.0*ltic;
    double r2 = rrose - 2.0*ltic;
    double x1 = xoff + r1*cos(f/rad);
    double y1 = yoff + r1*sin(f/rad);
    double x2 = xoff + r2*cos(f/rad);
    double y2 = yoff + r2*sin(f/rad);
    objects.push_back(new MuniDrawLine(x1,y1,x2,y2));
  }

  // scale
  const int xscale = 50;
  const int yscale = 30;
  objects.push_back(new MuniDrawLine(xscale,yscale,xscale+3600.*scale,yscale));
  objects.push_back(new MuniDrawText(xscale+3600.*scale/2,yscale+ltic,"1"));

  // stars
  if( catalogue.IsOk() ) {

    int n = catalogue.Nrows();

    int ira = GetColIndex(catalogue,rakey);
    int idec = GetColIndex(catalogue,deckey);
    int imag = GetColIndex(catalogue,magkey);

    //    wxLogDebug("%d %d %d",(int)ira,(int)idec,(int)imag);

    if( ira >= 0 && idec >= 0 ) {

      const double *a = catalogue.GetColumn(ira).GetCol_double();
      const double *d = catalogue.GetColumn(idec).GetCol_double();

      const float *mag = imag >= 0 ? catalogue.GetColumn(imag).GetCol_float() : 0;
      //      const float *mag = catalogue.GetColumn(imag).GetCol_float();
      //      wxASSERT(mag);

      for(int i = 0; i < n; i++) {

	/*
	  double a = catalogue.Cell(1,i);
	  double d = catalogue.Cell(2,i);
	  double mag = catalogue.Cell(17,i);
	*/

	double u = -(a[i] - alpha)*cos(d[i]/rad);
	double v = d[i] - delta;
	
	u = u / scale;
	v = v / scale;
	
	double dangle = angle/rad;
	double s = sin(dangle);
	double c = cos(dangle);
      
	double x = xoff + u*c + v*s;
	double y = yoff - u*s + v*c;
	
	if( mag ) {
	  double r = 1.0*pow(10.0,0.11*(maglim - mag[i]));
	  if( mag[i] > 1.0 && r > 2.5 )
	    objects.push_back(new MuniDrawCircle(x,y,r));
	}
	else {
	  objects.push_back(new MuniDrawCircle(x,y,1.0));
	  //      wxLogDebug("%f %f %f",(float)x,(float)y,(float)r);
	}
      }
    }
  }

  MuniLayer layer;
  layer.objects = objects;
  //  wxLogDebug("%d",(int)layer.objects.size());

  //  wxLogDebug("Mapping took: %f sec",sw.Time()/1000.0);

  return layer;
}

void MuniAstrometry::OnFit(wxCommandEvent& event)
{
  Bind(wxEVT_END_PROCESS,&MuniAstrometry::OnFitFinish,this);
  Bind(wxEVT_TIMER,&MuniAstrometry::OnTimer,this);

  MuniProcess *astrometry = new MuniProcess(&pipe,"astrometry");
  pipe.push(astrometry);
  
  astrometry->Write("PROJECTION = 'GNOMONIC'");

  if( init_fit ) {
    wxString a;
    astrometry->Write("MATCH = F");
    astrometry->Write("INITPAR = F");
    a.Printf("SCALE = %25.15e",scale);
    astrometry->Write(a);
    a.Printf("ANGLE = %20.15f",angle);
    astrometry->Write(a);
    a.Printf("CRVAL = %20.15f %20.15f",alpha,delta);
    astrometry->Write(a);
  }

  //  astrometry->Write("OUTPUT = '"+outfile+"'");
  astrometry->Write("WCSSAVE = T");
  astrometry->Write("KEY_ALPHA = '" + rakey + "'");
  astrometry->Write("KEY_DELTA = '" + deckey + "'");
  astrometry->Write("CAT = '"+catfile+"'");
  astrometry->Write("FILE = '"+file+"' '!"+outfile+"'");
  

  throbber->Show(true);
  throbber->Play();
  Layout();
  fitbutt->Enable(false);
  ftable->DeleteAllItems();

  running = true;
  timer.Start(100);
  pipe.Start();

  output_index = 0;
}

void MuniAstrometry::OnFitFinish(wxProcessEvent& event)
{
  wxLogDebug("MuniAstrometry::OnFitFinish");
  timer.Stop();
  running = false;

  Unbind(wxEVT_END_PROCESS,&MuniAstrometry::OnFitFinish,this);
  Unbind(wxEVT_TIMER,&MuniAstrometry::OnTimer,this);

  FitFill(pipe.GetOutput());

  throbber->Stop();
  fitbutt->Enable(true);
  throbber->Show(false);
  Layout();

  // inform user
  double s0min = 50.0; // parameter !!!
  MuniArtIcons ico(wxART_FRAME_ICON,wxSize(16,16));
  if( s0 < s0min )
    static_cast<xStatus *>(status)->SetTitle("Sucessfull fit.",ico.Icon(wxART_TICK_MARK));
  else
    static_cast<xStatus *>(status)->SetTitle("Fit failed.",ico.Icon(wxART_CROSS_MARK));
  Layout();
}


// parse output of astrometry fit and fill the data table
void MuniAstrometry::OnTimer(wxTimerEvent& event)
{
  FitFill(pipe.GetOutput());
}

void MuniAstrometry::FitFill(const wxArrayString& out)
{

  bool not_found = true;
  double s,pa,a0,d0;

  for(size_t i = output_index; i < out.GetCount(); i++) {

    wxString l(out[i]);

    wxRegEx re("=[AR]FIT>");
    wxASSERT(re.IsValid());
    if( re.Matches(l) ) {
      size_t i1,i2;
      re.GetMatch(&i1,&i2);
      l = l.Mid(i1+i2);
      //      wxLogDebug("FIT: "+l);
      wxStringInputStream ss(l);
      wxTextInputStream t(ss);
      t >> s0;
      t >> s;
      t >> pa;
      t >> a0;
      t >> d0;

      scale = s;
      angle = pa;
      alpha = a0;
      delta = d0;
      not_found = false;
      output_index = i;
    }
  }

  if( not_found ) return;

  wxVector<wxVariant> line;
  wxString a;
  a.Printf("%.3g",s0);
  line.push_back(wxVariant(a));
    
  /*
  if( sunit->GetLabel() == ARCSEC )
    s = 3600.0*s;
  else if( sunit->GetLabel() == ARCMIN )
    s = 60.0*s;
  */  

  a.Printf("%.3f",s);
  line.push_back(wxVariant(a));
    
  a.Printf("%.1f",pa);
  line.push_back(wxVariant(a));
    
  a.Printf("%.3f",a0);
  line.push_back(wxVariant(a));
    
  a.Printf("%.3f",d0);
  line.push_back(wxVariant(a));
      
  ftable->AppendItem(line);

  MuniAstrometryEvent ev(EVT_ASTROMETRY);
  ev.astrometry = true;
  ev.proj = proj;
  ev.scale = scale;
  ev.angle = angle;
  ev.xcen = xoff;
  ev.ycen = yoff;
  ev.acen = alpha;
  ev.dcen = delta;
  ev.layer = CreateMap();
  wxQueueEvent(GetParent(),ev.Clone());

}


void MuniAstrometry::Save(const wxString& filename)
{
  wxASSERT(! filename.IsEmpty());

  Bind(wxEVT_END_PROCESS,&MuniAstrometry::OnSaveFinish,this);

  MuniProcess *action = new MuniProcess(&pipe,"wcswrite");
  pipe.push(action);
  
  /*
  if( proj == "GNOMONIC" )
    action->Write("PROJECTION = '" + proj + "'");
  else
    wxFAIL_MSG("Unsuported projection");
  */
  
  action->Write("PROJECTION = 'GNOMONIC'");

  wxString a;
  a.Printf("CRVAL = %20.15f %20.15f",alpha,delta);
  action->Write(a);
  a.Printf("SCALE = %25.15e",scale);
  action->Write(a);
  a.Printf("ANGLE = %20.15f",angle);
  action->Write(a);

  //  action->Write("OUTPUT = '"+filename+"'");
  action->Write("FILE = '"+filename+"' '"+filename+"'");

  pipe.Start();

  throbber->Play();

}

void MuniAstrometry::OnSaveFinish(wxProcessEvent& event)
{
  wxLogDebug("MuniAstrometry::OnSaveFinish");

  Unbind(wxEVT_END_PROCESS,&MuniAstrometry::OnSaveFinish,this);

  throbber->Stop();
  Layout();

  SetReturnCode(wxID_APPLY);
  wxQueueEvent(GetParent(),new wxCloseEvent(wxEVT_CLOSE_WINDOW,this->GetId()));

}
